package propertymgmt.entities;

import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Version;

import propertymgmt.constants.Constants.PaymentType;

/**
 * Payment.java
 * 
 * Represents a Payment.  This class is an entity, which means
 * it will be persisted in the database
 * 
 * @author Brian
 *
 */
@SuppressWarnings("serial")
@Entity(name = "PAYMENT")

public class Payment implements Serializable, Comparable<Payment> {
	
	@Id
	@Column(name = "PAYMENT_ID", nullable = false)
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long paymentId;
	
	@Column(name = "PAYMENT_TYPE")
	@Enumerated(EnumType.STRING)
	private PaymentType type = null;
	
	@Column(name = "PAYMENT_NAME")
	private String paymentName = null;
	
	@Column(name = "DATE_RECEIVED", nullable = false)
	private Date dateReceived = null;
	
	@Column(name = "AMOUNT", nullable = false)
	private double amount = 0;
	
	@Column(name = "RENT_PAYMENT_MONTH")
	private Integer rentPaymentMonth;
	
	@Column(name = "RENT_PAYMENT_YEAR")
	private Integer rentPaymentYear;
	
	@Version
	@Column(name = "LAST_UPDATED_TIME")
	private Date updatedTime;
	
	@OneToOne(fetch=FetchType.LAZY)
	@Column(nullable = false)
	private List<Tenant> associatedTenants = null;

	/**
	 * Default constructor
	 */
	public Payment(){
		
	}
	
	/**
	 * Constructor
	 * 
	 * @param type PaymentType
	 * @param paymentName String identifier for Payment
	 * @param dateReceived Date Payment received
	 * @param amount Payment amount
	 * @param associatedTenants Tenants associated with this Payment
	 * @param rentPaymentMonth expected month for this Payment, if <code>type</code> is PaymentType.RENT (null otherwise)
	 * @param rentPaymentYear expected year for this Payment, if <code>type</code> is PaymentType.RENT (null otherwise)
	 */
	public Payment(PaymentType type, String paymentName, Date dateReceived, double amount,
			List<Tenant> associatedTenants, Integer rentPaymentMonth, Integer rentPaymentYear) {

		this.type = type;
		this.paymentName = paymentName;
		this.dateReceived = dateReceived;
		this.amount = amount;
		this.associatedTenants = associatedTenants;
		this.rentPaymentMonth = rentPaymentMonth;
		this.rentPaymentYear = rentPaymentYear;
	}
	
	/**
	 * @return the type
	 */
	public PaymentType getType() {
		return type;
	}

	/**
	 * @param type the type to set
	 */
	public void setType(PaymentType type) {
		this.type = type;
	}

	/**
	 * @param paymentName the paymentName to set
	 */
	public void setPaymentName(String paymentName) {
		this.paymentName = paymentName;
	}

	/**
	 * @return the paymentName
	 */
	public String getPaymentName() {
		return paymentName;
	}

	/**
	 * @param paymentName the paymentName to set
	 */
	public void setMonth(String paymentName) {
		this.paymentName = paymentName;
	}

	/**
	 * @return the dateReceived
	 */
	public Date getDateReceived() {
		return dateReceived;
	}

	/**
	 * @param dateReceived the dateReceived to set
	 */
	public void setDateReceived(Date dateReceived) {
		this.dateReceived = dateReceived;
	}

	/**
	 * @return the amount
	 */
	public double getAmount() {
		return amount;
	}

	/**
	 * @param amount the amount to set
	 */
	public void setAmount(double amount) {
		this.amount = amount;
	}

	/**
	 * @return the associatedTenants
	 */
	public List<Tenant> getAssociatedTenants() {
		return associatedTenants;
	}
	
	/**
	 * @param associatedTenants the associatedTenants to set
	 */
	public void setAssociatedTenants(List<Tenant> associatedTenants) {
		this.associatedTenants = associatedTenants;
	}
	
	/**
	 * Returns the expected rent payment month (null if <code>type</code> not PaymentType.RENT)
	 * @return Integer
	 */
	public Integer getRentPaymentMonth(){
		return rentPaymentMonth;
	}
	
	/**
	 * Returns the expected rent payment year (null if <code>type</code> not PaymentType.RENT)
	 * @return Integer
	 */
	public Integer getRentPaymentYear(){
		return rentPaymentYear;
	}
	
	/**
	 * Removes a Tenant from being associated with this Payment
	 * @param tenant Tenant
	 * @return true if successful
	 */
	public boolean removeTenant(Tenant tenant){
		return associatedTenants.remove(tenant);
	}
	
	/**
	 * Returns the number of date late this Payment was, given the month
	 * and year of the expected payment
	 * 
	 * @param month expected month of rent payment
	 * @param year expected year of rent payment
	 * @param dayLate the day of the month payments were considered late
	 * @return int
	 */
	public int getNumDaysLate(int month, int year, int dayLate){
		Calendar thisPaymentCal = Calendar.getInstance();
		thisPaymentCal.setTime(dateReceived);
		int thisPaymentMonth = thisPaymentCal.get(Calendar.MONTH);
		int thisPaymentDayOfMonth = thisPaymentCal.get(Calendar.DAY_OF_MONTH);
		int thisPaymentYear = thisPaymentCal.get(Calendar.YEAR);
		
		if(thisPaymentYear < year) return 0;
		if(thisPaymentMonth < month) return 0;
		
		Calendar lateDateCal = Calendar.getInstance();
		lateDateCal.set(Calendar.MONTH, month);
		lateDateCal.set(Calendar.DAY_OF_MONTH, dayLate);
		int lateDateMonth = lateDateCal.get(Calendar.MONTH);
		
		if(thisPaymentMonth == lateDateMonth){
			if(thisPaymentDayOfMonth < dayLate){
				return 0;
			}else {
				return (thisPaymentCal.get(Calendar.DAY_OF_MONTH) - lateDateCal.get(Calendar.DAY_OF_MONTH));
			}
		}else {
			// days late in due date month
			int daysLate = lateDateCal.getActualMaximum(Calendar.DAY_OF_MONTH) - lateDateCal.get(Calendar.DAY_OF_MONTH);
			// days late in payment month
			daysLate += thisPaymentCal.get(Calendar.DAY_OF_MONTH);
			// days late in months between
			Calendar testCal = Calendar.getInstance();
			for(int i = thisPaymentMonth+1; i < lateDateMonth-1; i++){
				testCal.set(Calendar.MONTH, i);
				daysLate += testCal.getActualMaximum(Calendar.DAY_OF_MONTH);
			}
			
			return daysLate;
		}
	}

	/**
	 * @return id
	 */
	public long getId() {
		return paymentId;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		long temp;
		temp = Double.doubleToLongBits(amount);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		result = prime
				* result
				+ ((associatedTenants == null) ? 0 : associatedTenants
						.hashCode());
		result = prime * result
				+ ((dateReceived == null) ? 0 : dateReceived.hashCode());
		result = prime * result
				+ ((paymentName == null) ? 0 : paymentName.hashCode());
		result = prime * result + ((type == null) ? 0 : type.hashCode());
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Payment other = (Payment) obj;
		if (Double.doubleToLongBits(amount) != Double
				.doubleToLongBits(other.amount))
			return false;
		if (associatedTenants == null) {
			if (other.associatedTenants != null)
				return false;
		} else if (!associatedTenants.equals(other.associatedTenants))
			return false;
		if (dateReceived == null) {
			if (other.dateReceived != null)
				return false;
		} else if (!dateReceived.equals(other.dateReceived))
			return false;
		if (paymentName == null) {
			if (other.paymentName != null)
				return false;
		} else if (!paymentName.equals(other.paymentName))
			return false;
		if (type != other.type)
			return false;
		return true;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString(){
		return type.toString() + " - $" + amount;
	}

	/* (non-Javadoc)
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(Payment o) {
		return dateReceived.compareTo(o.dateReceived);
	}
}